package com.dtb.trade.util;

import com.alibaba.fastjson.JSONObject;
import org.apache.commons.collections.MapUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @Author:ChengJian
 * @Description:
 * @Date: Created in 下午2:18 2018/7/13
 */
@Service
public class RedisService {

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    private String reidsKeyTitle = "dtb_";

    public String getString(String key) {
        return stringRedisTemplate.opsForValue().get(changereidsKeyTitle(key));
    }

    public String hget(String key, String field) {
        HashOperations<String, String, String> opsForHash = stringRedisTemplate.opsForHash();
        return opsForHash.get(changereidsKeyTitle(key), field);
    }

    public Map<String, String> hgetAll(String key) {
        HashOperations<String, String, String> opsForHash = stringRedisTemplate.opsForHash();
        return opsForHash.entries(changereidsKeyTitle(key));
    }

    /**
     * 根据key，将哈希表hashMap存到redis中。
     * 有则覆盖，无则插入
     * @param key
     * @param hashMap
     */
    public void hset(String key, Map<String, String> hashMap) {
        //把去空的hashMap再存到red is中
        HashOperations<String, String, String> opsForHash = stringRedisTemplate.opsForHash();
        opsForHash.putAll(changereidsKeyTitle(key), hashMap);
    }

    /**
     * 将哈希表 key 中的域 field 的值设为 value 。
     * 如果 key 不存在，一个新的哈希表被创建并进行 HSET 操作。
     * 如果域 field(hashKey) 已经存在于哈希表中，旧值将被覆盖。
     * @param key
     * @param hashKey
     * @param value
     */
    public void hset(String key, String hashKey, String value) {
        HashOperations<String, String, String> opsForHash = stringRedisTemplate.opsForHash();
        opsForHash.put(changereidsKeyTitle(key), hashKey, value);
    }

    public void expire(String key, long timeout, TimeUnit unit) {
        stringRedisTemplate.expire(changereidsKeyTitle(key), timeout, unit);
    }

    /**
     * 批量查询
     */
    public Map<String, JSONObject> getBatchJsonObject(final List<String> queryList) {
        if (CollectionUtils.isEmpty(queryList)) {
            return null;
        }
        final RedisSerializer<String> seria = stringRedisTemplate.getStringSerializer();
        RedisCallback<List<Object>> action = new RedisCallback<List<Object>>() {
            @Override
            public List<Object> doInRedis(RedisConnection connection) throws DataAccessException {
                connection.openPipeline();
                for (int i = 0; i < queryList.size(); i++) {
                    connection.get(seria.serialize(queryList.get(i)));
                }
                return connection.closePipeline();
            }
        };
        List<Object> execute = stringRedisTemplate.execute(action);
        if (CollectionUtils.isEmpty(execute)) {
            return null;
        }
        Map<String, JSONObject> resultMap = new HashMap<String, JSONObject>();
        for (int i = 0; i < execute.size(); i++) {
            if (execute.get(i) instanceof byte[]) {
                String resultStr = seria.deserialize((byte[]) execute.get(i));
                resultMap.put(queryList.get(i), JSONObject.parseObject(resultStr));
            }
        }
        return resultMap;
    }


    /**
     * 根据keys批量删除
     * @param keys
     */
    public void delete(List<String> keys) {
        stringRedisTemplate.delete(keys);
    }

    /**
     * 批量插入
     *
     * @param map
     */
    public void insertBatch(final Map<String, Object> map) {
        if (MapUtils.isEmpty(map)) {
            return;
        }
        final RedisSerializer<String> seria = stringRedisTemplate.getStringSerializer();
        RedisCallback<List<Object>> action = new RedisCallback<List<Object>>() {
            @Override
            public List<Object> doInRedis(RedisConnection connection) throws DataAccessException {
                connection.openPipeline();
                for (Map.Entry<String, Object> entry : map.entrySet()) {
                    connection.set(seria.serialize(entry.getKey()),
                            seria.serialize(JSONObject.toJSONString(entry.getValue())));
                }
                return connection.closePipeline();
            }
        };
        stringRedisTemplate.execute(action);
    }

    /**
     * 检查给定 key 是否存在。
     * @param key
     * @return  存在返回true
     */
    public boolean exists(String key){
        return stringRedisTemplate.hasKey(changereidsKeyTitle(key));
    }

    /**
     * 向redis存入key和value
     * 如果key已经存在 则覆盖
     * @param key
     * @param value
     */
    public void set(String key, String value){
        ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
        opsForValue.set(changereidsKeyTitle(key), value);
    }

    /**
     * 如果 key 已经存在并且是一个字符串， APPEND 命令将 value 追加到 key 原来的值的末尾。
     * 如果 key 不存在， APPEND 就简单地将给定 key 设为 value ，就像执行 SET key value 一样。
     * @param key
     * @param value
     */
    public void append(String key, String value){
        ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
        opsForValue.append(changereidsKeyTitle(key), value);
    }


    /**
     * 通过key向list头部添加字符串
     * @param key
     * @param strs 可以使一个string 也可以使string数组
     * @return 返回list的value个数
     */
    public Long lpush(String key, String...strs){
        ListOperations<String, String> opsForList = stringRedisTemplate.opsForList();
        long res = 0;
        int len = strs.length;
        if (len > 0) {
            for(int i = 0;i < len;i++){
                res += (opsForList.leftPush(changereidsKeyTitle(key), strs[i])).longValue();
            }
        }
        return res;
    }

    /**
     * 通过key向list尾部添加字符串
     * @param key
     * @param strs 可以使一个string 也可以使string数组
     * @return 返回list的value个数
     */
    public Long rpush(String key, String...strs){
        ListOperations<String, String> opsForList = stringRedisTemplate.opsForList();
        long res = 0;
        int len = strs.length;
        if (len > 0) {
            for(int i = 0;i < len;i++){
                res += (opsForList.rightPush(changereidsKeyTitle(key), strs[i])).longValue();
            }
        }
        return res;
    }

    /**
     * 通过key设置list指定下标位置的value
     * 如果下标超过list里面value的个数则返回false
     * @param key
     * @param index 从0开始
     * @param value
     * @return 成功返回true
     */
    public boolean lset(String key, Long index, String value){
        ListOperations<String, String> opsForList = stringRedisTemplate.opsForList();
        Long size = opsForList.size(changereidsKeyTitle(key));
        //先判断index下标大小是否超过list的大小
        if (index < size) {
            opsForList.set(changereidsKeyTitle(key), index, value);
            return true;
        }
        return false;
    }

    /**
     * 通过key获取list指定下标位置的value
     * 如果start 为 0 end 为 -1 则返回全部的list中的value
     * @param key
     * @param start
     * @param end
     * @return
     */
    public List<String> lrange(String key, long start, long end){
        ListOperations<String, String> opsForList = stringRedisTemplate.opsForList();
        return opsForList.range(changereidsKeyTitle(key), start, end);
    }
    /**
     * 通过key获取list的size
     *
     * @param key
     * @return
     */
    public Long lsize(String key){
        ListOperations<String, String> opsForList = stringRedisTemplate.opsForList();
        return opsForList.size(changereidsKeyTitle(key));
    }

    /**
     * <p>通过key从对应的list中删除指定的count个 和 value相同的元素</p>
     * @param key
     * @param count 当count为0时删除全部
     * count > 0 : 从表头开始向表尾搜索，移除与 value 相等的元素，数量为 count 。
     * count < 0 : 从表尾开始向表头搜索，移除与 value 相等的元素，数量为 count 的绝对值。
     * count = 0 : 移除表中所有与 value 相等的值。
     * @param value
     * @return 返回被删除的个数
     */
    public Long lrem(String key, long count, String value){
        ListOperations<String, String> opsForList = stringRedisTemplate.opsForList();
        return opsForList.remove(changereidsKeyTitle(key), count, value);
    }

    /**
     * 通过key从list尾部删除一个value,并返回该元素
     * @param key
     * @return
     */
    public String rpop(String key){
        ListOperations<String, String> opsForList = stringRedisTemplate.opsForList();
        return opsForList.rightPop(changereidsKeyTitle(key));
    }

    /**
     * 通过key从list头部删除一个value,并返回该元素
     * @param key
     * @return
     */
    public String lpop(String key){
        ListOperations<String, String> opsForList = stringRedisTemplate.opsForList();
        return opsForList.leftPop(changereidsKeyTitle(key));
    }

    /**
     * 通过key向zset中添加value,score,其中score就是用来排序的(权重)
     * 如果该value已经存在则根据score更新元素
     * @param key
     * @param
     * @return
     */
    public boolean zadd(String key, String value, double score){
        ZSetOperations<String, String> opsForZSet = stringRedisTemplate.opsForZSet();
        return opsForZSet.add(changereidsKeyTitle(key), value, score);
    }

    /**
     * 通过key返回指定score区间内zset中的value
     * @param key
     * @param max
     * @param min
     * @return
     */
    public Set<String> zrangebyScore(String key, double min, double max){
        ZSetOperations<String, String> opsForZSet = stringRedisTemplate.opsForZSet();
        return opsForZSet.rangeByScore(changereidsKeyTitle(key), min, max);
    }

    /**
     * 通过key将获取位置从start到end中zset的value
     * socre从大到小排序
     * @param key
     * @param
     * @param
     * @return
     */
    public Set<String> zrevrange(String key, long start, long end){
        ZSetOperations<String, String> opsForZSet = stringRedisTemplate.opsForZSet();
        return opsForZSet.reverseRange(changereidsKeyTitle(key), start, end);
    }

    /**
     * 通过key将获取score从start到end中zset的value
     * score从大到小排序
     * @param key 键
     * @param min 降序排序的起始权重
     * @param max 降序排序的结束权重
     * @return
     */
    public Set<String> zrevrangeByScore(String key, double min, double max) {
        ZSetOperations<String, String> opsForZSet = stringRedisTemplate.opsForZSet();
        return opsForZSet.reverseRangeByScore(changereidsKeyTitle(key), min, max);
    }

    /**
     * 通过key获取zset中value的score值
     * @param key
     * @param member
     * @return
     */
    public Double zscore(String key, String member){
        ZSetOperations<String, String> opsForZSet = stringRedisTemplate.opsForZSet();
        return opsForZSet.score(changereidsKeyTitle(key), member);
    }

    /**
     * 通过key获取zset中value的score值,返回Integer
     * @param key
     * @param member
     * @return
     */
    public Integer zscoreForInt(String key, String member) {
        Double score = zscore(changereidsKeyTitle(key), member);
        if (score == null) {
            return null;
        } else {
            return score.intValue();
        }
    }

    /**
     * 通过key删除在zset中指定的value
     * @param key
     * @param member
     * @return
     */
    public long zrem(String key, String member){
        ZSetOperations<String, String> opsForZSet = stringRedisTemplate.opsForZSet();
        return opsForZSet.remove(changereidsKeyTitle(key), member);
    }

    /**
     * 修改一个key 的 名字
     */
    public void rename(String oldkey, String newKey){
        stringRedisTemplate.rename(oldkey, newKey);
    }
    /**
     * 通过key删除指定score区间内zset中的value
     * @param key
     * @param max
     * @param min
     * @return
     */
    public void zrangeDelByScore(String key, double min, double max){
        ZSetOperations<String, String> opsForZSet = stringRedisTemplate.opsForZSet();
        opsForZSet.removeRangeByScore(changereidsKeyTitle(key), min, max);
    }

    /**
     * 通过key删除指定坐标区间内的zset中的value
     * @param key
     * @param start
     * @param end
     */
    public void zremRangeByRank(String key, long start, long end) {
        ZSetOperations<String, String> opsForZSet = stringRedisTemplate.opsForZSet();
        opsForZSet.removeRange(changereidsKeyTitle(key), start, end);
    }

    /**
     * 通过key 获取size
     * @param key
     * @param
     * @param
     * @return
     */
    public long zsize(String key){
        ZSetOperations<String, String> opsForZSet = stringRedisTemplate.opsForZSet();
        Long size = opsForZSet.size(changereidsKeyTitle(key));
        return size;
    }
    /**
     * 设定一个key的生命周期 （时间单位：秒）
     * @param key
     * @param
     * @param
     * @return
     */
    public void expire(String key, long timeOut){
        stringRedisTemplate.expire(changereidsKeyTitle(key), timeOut,TimeUnit.SECONDS);
    }
    /**
     * 删掉多个key（可以使用*作为替代任意长度字符）
     * 例delKeys("news:*");
     * @param startWithKeys 名字的开头，手动拼接*
     */
    public void delKeys(String startWithKeys){
        List<String> keys = this.keys(changereidsKeyTitle(startWithKeys));
        if (keys.size()>0) {
            this.delete(keys);
        }
    };

    /**
     * 向redis指定的db中存入key和value以及设置生存时间
     * 如果key已经存在 则覆盖
     * @param key
     * @param value
     * @param time    有效时间(默认时间单位为秒)
     * @param
     */
    public void set(String key, String value, Long time){
        ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
        // 默认时间单位为秒
        opsForValue.set(changereidsKeyTitle(key), value, time, TimeUnit.SECONDS);
    }

    /**
     * 通过key获取指定的value
     * @param key
     * @param
     * @return 没有返回null
     */
    public String get(String key) {
        return stringRedisTemplate.opsForValue().get(changereidsKeyTitle(key));
    }
    /**
     * 模糊查找keys 支持用*站位
     * @param
     */
    public List<String> keys(String vageuStr){
        Set<String> keys = stringRedisTemplate.keys(vageuStr);
        Iterator<String> iterator = keys.iterator();
        List<String> list = new ArrayList<>();
        while (iterator.hasNext()) {
            String string = (String) iterator.next();
            list.add(string);
        }
        return list;
    }
    /**
     * 根据key删除指定元素
     * @param
     */
    public void delete(String key) {
        stringRedisTemplate.delete(changereidsKeyTitle(key));
    }
    /**
     * 选择key 需不需要增加前缀
     * @param key
     * @return
     */
    private String changereidsKeyTitle(String key) {
        if (key.startsWith(reidsKeyTitle)) {
            return key;
        }
        if (key.contains("token")||key.contains("userInfo")||key.contains("userId")||key.contains("SMS_QUEUE")) {
            return key;
        }
        return reidsKeyTitle+key;
    }

}
